﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {


    /// <summary>
    /// 頂点バッファー クラス
    /// </summary>
    /// <remarks>
    /// 頂点バッファーは複数の頂点アレイ(<see cref="VertexArray"/>)を保持するコンテナクラスです。
    /// <img src="VertexBufferDiagram.png"/>
    /// これらの頂点アレイは順次シェーダーに送られ <c>Attribute</c> 変数としてアクセス可能です｡
    /// 頂点バッファーは､頂点アレイとその転送先である <c>Attribute</c> 変数の組みを保存します｡
    /// すべての頂点アレイは同じ頂点数である必要があります｡
    /// </remarks>
    /// <seealso cref="VertexArray"/>
    /// <seealso cref="Mesh"/>
    public class VertexBuffer : Object, IEnumerable<AttributeVariable> {

        #region Field
        List<AttributeVariable> attrs;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 設定済みの頂点アレイはありません｡
        /// </remarks>
        public VertexBuffer () {
            this.attrs = new List<AttributeVariable> ();
        }
        #endregion

        #region Property
        /// <summary>
        /// 頂点アレイの総数
        /// </summary>
        /// <remarks>
        /// 現在のこの頂点バッファーに保存されている <see cref="VertexArray"/> の個数です｡
        /// </remarks>
        public int VertexArrayCount {
            get { return attrs.Count; }
        }

        /// <summary>
        /// すべての <c>Attribute</c> 変数を列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 保存するすべての <c>Attribute</c> 変数を <see cref="AttributeVariable"/> として列挙する列挙子
        /// </remarks>
        /// <seealso cref="AttributeVariable"/>
        public IEnumerable<AttributeVariable> Attributes {
            get { return attrs; }
        }

        /// <summary>
        /// すべての頂点アレイを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 保存するすべての <see cref="VertexArray"/> を列挙する列挙子 
        /// </remarks>
        public IEnumerable<VertexArray> VertexArrays {
            get { return attrs.Select (x => x.VertexArray); }
        }

        /// <summary>
        /// 頂点数
        /// </summary>
        /// <remarks>
        /// 頂点アレイが保存する頂点の個数です｡すべての頂点アレイで頂点数は同一です｡
        /// 頂点アレイが何も登録されていない場合は0が返ります。
        /// </remarks>
        public int VertexCount {
            get {
                if (attrs.Count > 0) {
                    return attrs[0].VertexArray.VertexCount;
                }
                return 0;
            }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (attrs.Select (x => x.VertexArray));
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// 頂点アレイを登録します
        /// </summary>
        /// <remarks>
        /// この頂点バッファーに頂点アレイ <paramref name="varry"/> を登録し､<c>Attribute</c> 変数 
        /// <paramref name="name"/> に転送するようにセットします。
        /// すでに同名の <c>Attribute</c> 変数がセットされていた場合、古い方を削除して新しいもので置き換えます。
        /// 頂点バッファーが保存する頂点アレイの頂点数は、すべて同一である必要があります。
        /// </remarks>
        /// <param name="name"><c>Attribute</c> 変数名</param>
        /// <param name="varry">頂点アレイ</param>
        /// <exception cref="ArgumentNullException"><paramref name="varry"/> が <c>null</c></exception>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        /// <exception cref="ArgumentException"><see cref="VertexCount"/> &gt; 0 かつ <see cref="VertexCount"/> != <paramref name="varry"/>.<see cref="VertexArray.VertexCount"/></exception>
        public void AddVertexArray (string name, VertexArray varry) {
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            if (VertexCount > 0 && VertexCount != varry.VertexCount) {
                throw new ArgumentException ("All VertexCount must be same");
            }

            this.attrs.RemoveAll (x => x.Name == name);
            this.attrs.Add (new AttributeVariable (name, varry));
        }



        /// <summary>
        /// 頂点アレイを削除します
        /// </summary>
        /// <remarks>
        /// 登録されている頂点アレイから指定のものを削除します。そのような頂点アレイが登録されていなかった場合、単に無視します。
        /// </remarks>
        /// <param name="varry">頂点アレイ</param>
        /// <exception cref="ArgumentNullException"><paramref name="varry"/> が <c>null</c></exception>
        /// <returns>削除したら <c>true</c>、そうでなければ <c>false</c></returns>
        public bool RemoveVertexArray (VertexArray varry) {
            if (varry == null) {
                throw new ArgumentNullException ("VertexArray is null");
            }
            return this.attrs.RemoveAll (x => x.VertexArray == varry) > 0;
        }

        /// <summary>
        /// 頂点アレイを取得するインデクサー
        /// </summary>
        /// <remarks>
        /// 指定の名前 <paramref name="name"/> の頂点アレイを取得します。
        /// <paramref name="name"/> が登録されていない場合は、例外が発生します。
        /// このメソッドは <see cref="GetVertexArray"/> の別名です。
        /// </remarks>
        /// <param name="name"><c>Attbibute</c>変数名</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        /// <exception cref="InvalidOperationException"><paramref name="name"/> が見つからない</exception>
        /// <returns></returns>
        public VertexArray this[string name] {
            get { return GetVertexArray (name); }
        }

        /// <summary>
        /// <c>Attribute</c> 変数を取得します
        /// </summary>
        /// <remarks>
        /// 指定の名前 <paramref name="name"/> の <c>Attribute</c> 変数を取得します。
        /// その名前が登録されていない場合は例外が発生します。
        /// </remarks>
        /// <param name="name"><c>Attribute</c> 変数名</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c> または空文字列</exception>
        /// <returns><c>Attribute</c> 変数をあらわす <see cref="AttributeVariable"/> 構造体</returns>
        /// <seealso cref="AttributeVariable"/>
        public AttributeVariable GetAttribute (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            return attrs.First (x => x.Name == name);
        }

        /// <summary>
        /// 頂点アレイを取得します
        /// </summary>
        /// <remarks>
        /// 指定の <c>Attribute</c> 変数名で登録された頂点アレイを取得します。
        /// 該当する名前の <c>Attribute</c> 変数が登録されていない場合は例外が発生します。
        /// </remarks>
        /// <param name="name"><c>Attribute</c> 変数名</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> が <c>null</c></exception>
        /// <exception cref="InvalidOperationException"><paramref name="name"/> が見つからない</exception>
        /// <returns>頂点アレイをあらわす <see cref="VertexArray"/> オブジェクト</returns>
        public VertexArray GetVertexArray (string name) {
            if (name == null || name == "") {
                throw new ArgumentNullException ("Name is null or empty");
            }
            return (from x in attrs
                   where x.Name == name
                   select x.VertexArray).First();
        }

        /// <inheritdoc/>
        IEnumerator<AttributeVariable> IEnumerable<AttributeVariable>.GetEnumerator () {
            return ((IEnumerable<AttributeVariable>)attrs).GetEnumerator ();
        }

        /// <inheritdoc/>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return attrs.GetEnumerator ();
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("VertexBuffer: Attributes={0}", VertexArrayCount);
        }
        #endregion


    }
}
